package com.mo.tools.excel;


import cn.hutool.core.lang.Assert;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.*;

public class EasyExcelUtils {

    private static final Logger log = LoggerFactory.getLogger(EasyExcelUtils.class);

    /**
     * 阻塞队列大小限制
     */
    private static final int BLOCK_QUEUE_SIZE = 5000;

    /**
     * 同一个允许相同任务只允许同时导入3次
     */
    private static final int RATE_LIMIT_COUNT = 3;

    private static final ThreadFactory namedThreadFactory = new DefaultThreadFactory("excelImportPool-");
    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 20,
            2L, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>(BLOCK_QUEUE_SIZE),
            namedThreadFactory,
            new ThreadPoolExecutor.CallerRunsPolicy());


    /**
     * EasyExcel读取增强
     *
     * @param readerSheetBuilder
     */
    public synchronized static void read(ExcelReaderSheetBuilder readerSheetBuilder, ExcelImportListener excelImportListener) {
        String userId = excelImportListener.getUserId();
        String traceId = excelImportListener.getTraceId();
        Assert.notBlank(traceId, "链路id不能为空");
        Assert.notBlank(userId, "用户id不能为空");
        log.info("traceId[{}] EasyExcelUtils 读取任务开始", traceId);

        try {
            excelImportListener.setRealExcelImportListener(excelImportListener);
            String listenerName = excelImportListener.getClass().getSimpleName();
            // 检查限流
            checkRateLimit(traceId, userId, listenerName);

            // 线程池线任务限制
            BlockingQueue<Runnable> executorQueue = threadPoolExecutor.getQueue();
            System.out.println("队列大小： " + executorQueue.size());
            if (executorQueue.size() + 1 > BLOCK_QUEUE_SIZE) {
                log.error("traceId[{}] EasyExcelUtils 队列已满 不能提交新任务 队列大小[{}]", traceId, BLOCK_QUEUE_SIZE);
                throw new RuntimeException("导入任务过多, 请稍后再试");
            }
            // 异步导入
            CompletableFuture.runAsync(readerSheetBuilder::doRead, threadPoolExecutor)
                    .exceptionally(throwable -> {
                        log.error("traceId[{}], EasyExcelUtils 异常", traceId, throwable);
                        return null;
                    });
            // 更新限流
            updateRateLimit(traceId, userId, listenerName);
        } catch (Exception e) {
            log.error("traceId[{}], EasyExcelUtils 异常", traceId, e);
            throw new RuntimeException("导入失败");
        }
    }

    /**
     * 检查限流
     */
    private static void checkRateLimit(String traceId, String userId, String clazzName) {
        Assert.notBlank(traceId, "链路id不能为空");
        Assert.notBlank(userId, "用户id不能为空");
        Assert.notBlank(clazzName, "Listener 类名不能为空");
        try {
            String key = userId + "_" + clazzName;
            RedisTemplate<String, String> redisTemplate = getRedisTemplate();
            String value = redisTemplate.opsForValue().get(key);
            log.info("key[{}] 当前限流次数 [{}]", key, value);
            if (value != null && Integer.parseInt(value) > RATE_LIMIT_COUNT) {
                log.error("traceId[{}], EasyExcelUtils 校验限流异常", traceId);
                throw new RuntimeException("超过导入任务限制，请等待其它任务完成");
            }
        } catch (Exception e) {
            log.error("traceId[{}], EasyExcelUtils 校验限流异常", traceId, e);
            throw new RuntimeException("导入失败");
        }

    }

    /**
     * 限流处理
     */
    private static void updateRateLimit(String traceId, String userId, String clazzName) {
        Assert.notBlank(traceId, "链路id不能为空");
        Assert.notBlank(userId, "用户id不能为空");
        Assert.notBlank(clazzName, "Listener 类名不能为空");
        try {
            String key = userId + "_" + clazzName;
            RedisTemplate<String, String> redisTemplate = getRedisTemplate();
            String value = redisTemplate.opsForValue().get(key);
            if (ObjectUtils.isEmpty(value)) {
                redisTemplate.opsForValue().set(key, "1", 2, TimeUnit.HOURS);
            } else {
                redisTemplate.opsForValue().set(key, String.valueOf(Integer.parseInt(value) + 1), 2, TimeUnit.HOURS);
            }

        } catch (Exception e) {
            log.error("traceId[{}], EasyExcelUtils 更新限流异常", traceId, e);
            throw new RuntimeException("导入失败");
        }

    }


    /**
     * 获取redisTemplate
     * @return
     */
    public static RedisTemplate<String, String> getRedisTemplate() {
        RedisTemplate<String, String> redisTemplate = SpringUtil.getBean("redisTemplate");
        redisTemplate = redisTemplate == null ? SpringUtil.getBean("stringRedisTemplate") : redisTemplate;
        return redisTemplate;
    }
}
